/*
Copyright (C) 2015 Marien Raat

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <groundWaterMap.h>

#include <normalizer.h>

#include <math.h>
#include <iostream>

GroundWaterMapGenerator::GroundWaterMapGenerator(std::mt19937 *mt)
    : fractalNoiseGenerator(mt)
{
    firstMap = true;
}

double *GroundWaterMapGenerator::generateGroundWaterMap(
    unsigned width,
    unsigned height,
    double *heightmap,
    bool *waterMap,
    double *heatMap,
    bool overwriteOldMap)
{
    // Re-use or create a new groundWaterMap as needed
    if (firstMap || !overwriteOldMap) {
        groundWaterMap = new double[width * height];
    } else if (width != previousWidth ||
               height != previousHeight) {
        delete groundWaterMap;
        groundWaterMap = new double[width * height];
    }
    // Update parameters for next call
    firstMap = false;
    previousWidth = width;
    previousHeight = height;

    // Generate a noiseMap to represent factors that aren't otherwise
    // simulated.
    double *noiseMap = getNoiseMap(width, height);

    // Populate the groundWaterMap with groundWater values
    for (unsigned i = 0; i < width * height; i++) {
        unsigned x = i % width;
        unsigned y = i / width;
        groundWaterMap[i] = getGroundWaterValue(x, y, width, height,
                                          heightmap, waterMap,
                                          heatMap, noiseMap);
    }

    Normalizer<double>::normalize(groundWaterMap, groundWaterMap,
                                  width * height, 0.0, 1.0);

    // All water has 100% groundWater for clearity
    for (unsigned i; i < width * height; i++)
        if (waterMap[i]) groundWaterMap[i] = 1.0;

    return groundWaterMap;
}

double *GroundWaterMapGenerator::getNoiseMap(
    unsigned width,
    unsigned height)
{
    const unsigned ammountOfValues = 3 * 2;
    double values[ammountOfValues] = {
        256, 1.0,
        128, 0.8,
        32, 0.1
    };

    fractalNoiseGenerator.generateFractalNoiseFromValues
        (values, ammountOfValues, width, height, true);

    return fractalNoiseGenerator.noiseMap;
}

double GroundWaterMapGenerator::getGroundWaterValue(
    unsigned x,
    unsigned y,
    unsigned width,
    unsigned height,
    double *heightmap,
    bool *waterMap,
    double *heatMap,
    double *noiseMap)
{
    unsigned indexInMaps = x + y * width;

    double heightValue = heightmap[indexInMaps];
    double waterScore = getWaterScore(x, y, width, height, waterMap);
    double heat = heatMap[indexInMaps];
    double noiseValue = noiseMap[indexInMaps];

    return (- 0.1 * heightValue
            + 0.5 * waterScore
            - 0.3 * heat
            + 0.05 * noiseValue);
}

double GroundWaterMapGenerator::getWaterScore(
    unsigned x,
    unsigned y,
    unsigned width,
    unsigned height,
    bool *waterMap)
{
    double waterDistance = getWaterDistance(x, y, width, height,
                                            waterMap);

    return 1.0 / (1.0 + (waterDistance / 200.0));
}

// Searches in squares
// Not the most efficient way to do it
double GroundWaterMapGenerator::getWaterDistance(
    unsigned x,
    unsigned y,
    unsigned width,
    unsigned height,
    bool *waterMap)
{
    if (waterMap[x + y * width]) return 0.0;

    unsigned searchSquareSize;
    double minDistance = width * height + 1;
    int searchSquareSizeFound = -1;
    for (searchSquareSize = 2;
         searchSquareSize <= std::max(width, height) &&
             searchSquareSize <= 100;
         searchSquareSize += 2) {
        // Test the outline of a square with a size of
        // searchSquareSize around the point to find a place with
        // water.
        int ySquare, xSquare;

        // Test the top and the bottom of the square
        for (xSquare = x - searchSquareSize / 2;
             xSquare <= x + searchSquareSize / 2;
             xSquare++) {
            ySquare = y - searchSquareSize / 2;
            if (xSquare >= 0 && xSquare < width &&
                ySquare >= 0 && ySquare < height &&
                waterMap[xSquare + ySquare * width]) {
                double distance = sqrt(
                    pow((double) abs(x - xSquare), 2.0) +
                    pow((double) abs(y - ySquare), 2.0));
                minDistance = std::min(minDistance, distance);
            }
            ySquare = y + searchSquareSize / 2;
            if (xSquare >= 0 && xSquare < width &&
                ySquare >= 0 && ySquare < height &&
                waterMap[xSquare + ySquare * width]) {
                double distance = sqrt(
                    pow((double) abs(x - xSquare), 2.0) +
                    pow((double) abs(y - ySquare), 2.0));
                minDistance = std::min(minDistance, distance);
            }
        }

        // Test the left and the right edges of the square
        for (ySquare = y - searchSquareSize / 2;
             ySquare <= y + searchSquareSize / 2;
             ySquare++) {
            xSquare = x - searchSquareSize / 2;
            if (xSquare >= 0 && xSquare < width &&
                ySquare >= 0 && ySquare < height &&
                waterMap[xSquare + ySquare * width]) {
                double distance = sqrt(
                    pow((double) abs(x - xSquare), 2.0) +
                    pow((double) abs(y - ySquare), 2.0));
                minDistance = std::min(minDistance, distance);
            }
            xSquare = x + searchSquareSize / 2;
            if (xSquare >= 0 && xSquare < width &&
                ySquare >= 0 && ySquare < height &&
                waterMap[xSquare + ySquare * width]) {
                double distance = sqrt(
                    pow((double) abs(x - xSquare), 2.0) +
                    pow((double) abs(y - ySquare), 2.0));
                minDistance = std::min(minDistance, distance);
            }
        }

        if (minDistance < width * height &&
            searchSquareSizeFound == -1) {
            searchSquareSizeFound = searchSquareSize;
        }

        if (searchSquareSizeFound != -1 &&
            (searchSquareSize > (sqrt(2.0f) * searchSquareSizeFound) ||
             searchSquareSize > ((2.0f * minDistance) + 1))) {
            break;
        }
    }

    return minDistance < 50.0 ? minDistance : 50.0;
}
